﻿#region Usings

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Rail.Net.Common.Model;
using Rail.Net.Core.Interfaces.Data.Cacheable;

#endregion

namespace Rail.Net.Data.Caching
{
    public class MemoryCacheProvider : ICacheProvider
    {
        private static ConcurrentDictionary<string, CacheItem> _dictionary;
        private static MemoryCacheProvider _instance;
        private static readonly object Locker = new object();

        private MemoryCacheProvider()
        {
        }

        #region ICacheProvider Members

        public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query)
        {
            var key = GetKey(query);

            var item = _dictionary.GetOrAdd(
                key,
                (keyToFind) => new CacheItem {Item = query.ToList(), AdditionTime = DateTime.Now});

            return ((List<T>) item.Item);
        }


        public IEnumerable<T> GetOrCreateCache<T>(IQueryable<T> query, TimeSpan cacheDuration)
        {
            var key = GetKey(query);

            var item = _dictionary.GetOrAdd(
                key,
                (keyToFind) => new CacheItem {Item = query.ToList(), AdditionTime = DateTime.Now});

            if (DateTime.Now.Subtract(item.AdditionTime) > cacheDuration)
            {
                item = _dictionary.AddOrUpdate(
                    key,
                    new CacheItem {Item = item.Item, AdditionTime = DateTime.Now},
                    (keyToFind, oldItem) => new CacheItem {Item = query.ToList(), AdditionTime = DateTime.Now});
            }
            return ((List<T>) item.Item);
        }

        public void RemoveFromCache<T>(IQueryable<T> query)
        {
            var key = GetKey(query);
            CacheItem item = null;
            _dictionary.TryRemove(key, out item);
        }

        public void ClearCache<T>()
        {
            var keys = _dictionary.Keys.Where(k => k.StartsWith(typeof (T).FullName)).ToList();
            CacheItem item;
            keys.ForEach(k => _dictionary.TryRemove(k, out item));
        }

        #endregion

        public static MemoryCacheProvider GetInstance()
        {
            lock (Locker)
            {
                if (_dictionary == null)
                {
                    _dictionary = new ConcurrentDictionary<string, CacheItem>();
                }

                if (_instance == null)
                {
                    _instance = new MemoryCacheProvider();
                }
            }
            return _instance;
        }

        private static string GetKey<T>(IQueryable<T> query)
        {
            var key = string.Concat(typeof(T).FullName, "\n\r", query.ToString());
            return key;
        }
    }
}